//------------------------------------------------------------------------------
// <copyright file="RapidJsonImpl.h" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

#pragma once

#ifdef USE_RAPIDJSON
#define JSON_IMPLEMENTED

#include <assert.h>
#include "document.h"
#include "writer.h"
#include "stringbuffer.h"
#include "IJsonNode.h"

namespace Microsoft { namespace Internal { namespace GamesTest { namespace Rpc { namespace Server
{
	/// <summary>
	/// IJsonNode implemented with rapidJson.
	/// </summary>
	class JsonNode: public IJsonNode
	{
	private:
		/// <summary>
		/// rapidJson templates typed for Unicode.
		/// </summary>
		typedef rapidjson::GenericDocument<rapidjson::UTF16<>> JsonDocument;
		typedef rapidjson::GenericDocument<rapidjson::UTF16<>>::AllocatorType JsonAllocator;
		typedef rapidjson::GenericValue<rapidjson::UTF16<>> JsonValue;
		typedef rapidjson::GenericStringBuffer<rapidjson::UTF16<>> JsonBuffer;
		typedef rapidjson::Writer<JsonBuffer, rapidjson::UTF16<>, rapidjson::UTF16<>> JsonWriter;

	private:
		/// <summary>
		/// rapidJson::GenericDocument used for parsing as well as storing the allocator.
		/// </summary>
		JsonDocument m_doc;

		/// <summary>
		/// Implementation that points to this document or references another document initialized with.
		/// </summary>
		/// <remarks>This is used to avoid unnecessary copying when returning a JsonNode by value.</remarks>
		JsonValue& m_imp;

		/// <summary>
		/// rapidJson::GenericValue::Allocator that points to document's allocator or reference's allocator.
		/// </summary>
		JsonAllocator* m_alloc;

	private:
		/// <summary>
		/// Constructor to transfer ownership of internal implementation.
		/// </summary>
		/// <param name="imp">Internal implementation.</param>
		JsonNode(JsonDocument& imp): m_imp(m_doc)
		{
			m_doc = std::move(imp);
			m_alloc = &m_doc.GetAllocator();
		}

		/// <summary>
		/// Constructor to reference another internal implementation.
		/// </summary>
		/// <param name="imp">Internal implementation.</param>
		JsonNode(JsonValue& imp, JsonAllocator& alloc): m_imp(imp), m_alloc(&alloc) {}

		/// <summary>
		/// Validate array index for get functions.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <remarks>
		/// Throws std::out_of_range if index is out of range.
		/// </remarks>
		JsonValue& CheckRange(unsigned long index) const
		{
			if (!m_imp.IsArray() || index >= m_imp.Size())
			{
				char range[16] = {0};
				_itoa_s(index, range, sizeof(range) / sizeof(char), 10);

				throw std::out_of_range(range);
			}

			return m_imp[index];
		}

		/// <summary>
		/// Raise range error for keys.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		static void RangeError(const wchar_t* key)
		{
			std::wstring what(key);
			throw std::out_of_range(std::string(what.begin(), what.end()));
		}

		/// <summary>
		/// Validate key for get functions.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		JsonValue::MemberIterator CheckRange(const wchar_t* key) const
		{
			JsonValue::MemberIterator member = m_imp.FindMember(key);

			if (member == m_imp.MemberEnd())
			{
				RangeError(key);
			}

			return member;
		}

	public:
		/// <summary>
		/// Default constructor.
		/// </summary>
		JsonNode(): m_imp(m_doc)
		{
			m_doc.SetObject();
			m_alloc = &m_doc.GetAllocator();
		}

		/// <summary>
		/// Deep copy constructor.
		/// </summary>
		/// <param name="other">Node to deep-copy.</param>
		JsonNode(const JsonNode& other): m_imp(m_doc)
		{
			m_alloc = &m_doc.GetAllocator();
			m_doc.CopyFrom(other.m_imp, m_doc.GetAllocator());
		}

		/// <summary>
		/// Move constructor.
		/// </summary>
		/// <param name="other">Node to move into this one.</param>
		JsonNode(JsonNode&& other): m_imp(m_doc)
		{
			assert(&other.m_imp == &other.m_doc);
			m_doc = std::move(other.m_doc);
			m_alloc = &m_doc.GetAllocator();
		}

	public:
		/// <summary>
		/// Gets the node type.
		/// </summary>
		/// <returns>Node type.</returns>
		Types GetType() const
		{
			switch (m_imp.GetType())
			{
			default:
			case rapidjson::kObjectType:
				return Types::Object;
			case rapidjson::kFalseType:
			case rapidjson::kTrueType:
				return Types::Boolean;			
			case rapidjson::kArrayType:
				return Types::Array;
			case rapidjson::kStringType:
				return Types::String;
			case rapidjson::kNumberType:
				return Types::Number;
			}
		}

		/// <summary>
		/// Checks whether a child node exists.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns><c>true</c> if child node exists, <c>false</c> otherwise.</returns>
		bool Exists(const wchar_t* key) const
		{
			return m_imp.FindMember(key) != m_imp.MemberEnd();
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, double value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			auto member = m_imp.FindMember(key);			

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					value,
					*m_alloc);
			}
			else
			{
				member->value = value;
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, bool value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			auto member = m_imp.FindMember(key);			

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					value,
					*m_alloc);
			}
			else
			{
				member->value = value;
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, int value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			auto member = m_imp.FindMember(key);			

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					value,
					*m_alloc);
			}
			else
			{
				member->value = value;
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, const wchar_t* value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			JsonValue copy(value, (rapidjson::SizeType)wcslen(value), *m_alloc);

			auto member = m_imp.FindMember(key);

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					copy,
					*m_alloc);
			}
			else
			{
				member->value = copy;
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, JsonString value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			JsonValue copy(value, (rapidjson::SizeType)value.GetLength(), *m_alloc);

			auto member = m_imp.FindMember(key);

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					copy,
					*m_alloc);
			}
			else
			{
				member->value = copy;
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, const JsonNode& value)
		{
			if (!m_imp.IsObject())
			{
				m_imp.SetObject();
			}

			JsonValue copy;
			copy.CopyFrom(value.m_imp, *m_alloc);

			auto member = m_imp.FindMember(key);

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					copy,
					*m_alloc);
			}
			else
			{
				member->value = copy;
			}
	}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, double value)
		{
			if (!m_imp.IsArray())
			{
				m_imp.SetArray()[index] = value;
			}
			else
			{
				m_imp[index] = value;
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, bool value)
		{
			if (!m_imp.IsArray())
			{
				m_imp.SetArray()[index] = value;
			}
			else
			{
				m_imp[index] = value;
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, int value)
		{
			if (!m_imp.IsArray())
			{
				m_imp.SetArray()[index] = value;
			}
			else
			{
				m_imp[index] = value;
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, JsonString value)
		{
			JsonValue copy(value, (rapidjson::SizeType)value.GetLength(), *m_alloc);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray()[index] = copy;
			}
			else
			{
				m_imp[index] = copy;
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, const JsonNode& value)
		{
			JsonValue copy;
			copy.CopyFrom(value.m_imp, *m_alloc);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray()[index] = copy;
			}
			else
			{
				m_imp[index] = copy;
			}
		}

		/// <summary>
		/// Gets a child node.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		JsonNode GetValue(const wchar_t* key)
		{
			return JsonNode(CheckRange(key)->value, *m_alloc);
		}

		/// <summary>
		/// Gets an array node, creating if doesn't exist.
		/// </summary>
		/// <param name="key">Array node key.</param>
		/// <returns>Array node.</returns>
		JsonNode GetArrayValue(const wchar_t* key)
		{
			auto member = m_imp.FindMember(key);

			if (member == m_imp.MemberEnd())
			{
				m_imp.AddMember(
					JsonValue(key, (rapidjson::SizeType)wcslen(key), *m_alloc).Move(),
					JsonValue(rapidjson::kArrayType).Move(),
					*m_alloc);

				return JsonNode(m_imp.FindMember(key)->value, *m_alloc);
			}
			
			return JsonNode(member->value, *m_alloc);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		JsonNode GetValue(unsigned long index)
		{
			return JsonNode(CheckRange(index), *m_alloc);
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		double GetNumericValue(const wchar_t* key) const
		{
			auto member = CheckRange(key);
			
			if (member->value.GetType() != rapidjson::kNumberType)
			{
				// Return default value for the type on mismatch.
				return 0.0;
			}
			else
			{
				return member->value.GetDouble();
			}
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		int GetIntegerValue(const wchar_t* key) const
		{
			auto member = CheckRange(key);

			if (member->value.GetType() != rapidjson::kNumberType)
			{
				// Return default value for the type on mismatch.
				return 0;
			}
			else
			{
				if (member->value.IsInt())
				{
					return member->value.GetInt();
				}
				else
				{
					return (int)member->value.GetDouble();
				}
			}
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		double GetNumericValue(unsigned long index) const
		{
			JsonValue& value = CheckRange(index);

			if (value.GetType() != rapidjson::kNumberType)
			{
				// Return default value for the type on mismatch.
				return 0.0;
			}
			else
			{
				return value.GetDouble();
			}
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		int GetIntegerValue(unsigned long index) const
		{
			JsonValue& value = CheckRange(index);

			if (value.GetType() != rapidjson::kNumberType)
			{
				// Return default value for the type on mismatch.
				return 0;
			}
			else
			{
				return value.GetInt();
			}
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		bool GetBooleanValue(const wchar_t* key) const
		{
			auto member = CheckRange(key);

			if (member->value.GetType() != rapidjson::kTrueType && member->value.GetType() != rapidjson::kFalseType)
			{
				// Return default value for the type on mismatch.
				return false;
			}
			else
			{
				return member->value.GetBool();
			}
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		bool GetBooleanValue(unsigned long index) const
		{
			JsonValue& value = CheckRange(index);

			if (value.GetType() != rapidjson::kTrueType && value.GetType() != rapidjson::kFalseType)
			{
				// Return default value for the type on mismatch.
				return false;
			}
			else
			{
				return value.GetBool();
			}
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		JsonString GetStringValue(const wchar_t* key) const
		{
			JsonValue::MemberIterator member = CheckRange(key);

			if (member->value.GetType() != rapidjson::kStringType)
			{
				// Return default value for the type on mismatch.
				return JsonString::EmptyString();
			}
			else
			{
				return JsonString(member->value.GetString(), (unsigned long)member->value.GetStringLength());
			}
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		JsonString GetStringValue(unsigned long index) const
		{
			JsonValue& value = CheckRange(index);

			if (value.GetType() != rapidjson::kStringType)
			{
				// Return default value for the type on mismatch.
				return JsonString::EmptyString();
			}
			else
			{
				return JsonString(value.GetString(), (unsigned long)value.GetStringLength());
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(const JsonNode& value)
		{
			JsonValue copy;
			copy.CopyFrom(value.m_imp, *m_alloc);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray().PushBack(copy, *m_alloc);
			}
			else
			{
				m_imp.PushBack(copy, *m_alloc);
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(double value)
		{
			JsonValue copy(value);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray().PushBack(copy, *m_alloc);
			}
			else
			{
				m_imp.PushBack(copy, *m_alloc);
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(int value)
		{
			JsonValue copy(value);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray().PushBack(copy, *m_alloc);
			}
			else
			{
				m_imp.PushBack(copy, *m_alloc);
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(bool value)
		{
			JsonValue copy(value);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray().PushBack(copy, *m_alloc);
			}
			else
			{
				m_imp.PushBack(copy, *m_alloc);
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(const wchar_t* value)
		{
			JsonValue copy(value, (rapidjson::SizeType)wcslen(value), *m_alloc);

			if (!m_imp.IsArray())
			{
				m_imp.SetArray().PushBack(copy, *m_alloc);
			}
			else
			{
				m_imp.PushBack(copy, *m_alloc);
			}
		}

		/// <summary>
		/// Check whether object or array are empty.
		/// </summary>
		/// <returns><c>true</c> if object or array is empty, <c>false</c> otherwise.</returns>
		bool Empty() const
		{
			if (m_imp.IsObject())
				return m_imp.ObjectEmpty();
			else if (m_imp.IsArray())
				return m_imp.Empty();

			// If not object and not array, assuming this node was moved into another node.
			return true;
		}

		/// <summary>
		/// Gets array item count.
		/// </summary>
		/// <returns>Array item count if node is an array.</returns>
		unsigned long GetCount() const
		{
			if (m_imp.IsArray())
			{
				return (unsigned long)m_imp.Size();
			}

			return 0;
		}

		/// <summary>
		/// Clears an array.
		/// </summary>
		void Clear()
		{
			if (m_imp.IsArray())
			{
				m_imp.Clear();
			}
		}

		/// <summary>
		/// Serializes to string.
		/// </summary>
		/// <returns>String buffer the node was serialized to.</returns>
		JsonString Serialize() const
		{
			JsonBuffer buffer;
			JsonWriter writer(buffer);

			m_imp.Accept(writer);

			// Copy string from the buffer.
			return JsonString((wchar_t*)buffer.GetString(), (unsigned long)wcslen(buffer.GetString()));
		}

		/// <summary>
		/// Parses from string.
		/// </summary>
		/// <param name="string">String to parse from.</param>
		/// <remarks>
		/// Throws std::invalid_argument if parsing fails.
		/// </remarks>
		void Parse(JsonString string)
		{
			// Cannot parse when initialized from another document's node.
			if (&m_doc != &m_imp)
			{
				return;
			}

			// Parse only if allocator was not moved.
			if (&m_doc.GetAllocator() != nullptr)
			{
				m_doc.Parse(string);

				if (m_doc.HasParseError())
				{
					throw std::invalid_argument("string parse error");
				}
			}
		}

	public:
		/// <summary>
		/// Deep copy assignment.
		/// </summary>
		/// <param name="other">Another node to copy.</param>
		/// <returns>Self reference.</returns>
		JsonNode& operator=(const JsonNode& other)
		{
			m_doc.CopyFrom(other.m_imp, *m_alloc);
			return *this;
		}

		/// <summary>
		/// Move assignment.
		/// </summary>
		/// <param name="other">Another node to move.</param>
		/// <returns>Self reference.</returns>
		JsonNode& operator=(JsonNode&& other)
		{
			assert(&other.m_imp == &other.m_doc);
			assert(&m_imp == &m_doc);
			m_doc = std::move(other.m_doc);
			m_alloc = &m_doc.GetAllocator();
			return *this;
		}
	};

}}}}}

#endif // RAPIDJSON